
#include "../libpatches.h"
#include "trigger.h"
#include "../garbage.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <float.h>

struct trigger_storage {
	int * last;
	int dir;
	int wrap;
	sample in;
};

typedef struct trigger_storage * trigger_storage;

static void module_trigger_processor(module m);
void module_trigger_customizer(module m, char * name, char * value);

void module_trigger_initializer(module m) {
	trigger_storage s;

	m->customizer = module_trigger_customizer;
	m->processor = module_trigger_processor;
	m->storage = (void *) new_garbage(1, sizeof(struct trigger_storage));
	s = (trigger_storage) m->storage;
	s->wrap = 1;
	s->dir = 1;
}

void module_trigger_customizer(module m, char * name, char * value) {
	trigger_storage s;
	int last;
	int c;
	int * n;

	s = (trigger_storage) m->storage;

	customizer_count_inputs_only(m, name, value);
	if (m->input_count && ! m->inputs) {
		make_named_inputs(m, m->input_count, 1);
		make_named_outputs(m, m->input_count, 1);
		m->input_names[0] = "in";
		m->output_names[0] = "out";
		s->last = (void *) new_garbage(m->input_count, sizeof(int));
	} else if (strcmp(name, "mode") == 0) {
		if (strcmp(value, "up") == 0) {
			s->wrap = 0;
			s->dir = 1;
		} else if (strcmp(value, "wrapup") == 0) {
			s->wrap = 1;
			s->dir = 1;
		} else if (strcmp(value, "down") == 0) {
			s->wrap = 0;
			s->dir = -1;
		} else if (strcmp(value, "wrapdown") == 0) {
			s->wrap = 1;
			s->dir = -1;
		} else if (strcmp(value, "both") == 0) {
			s->wrap = 0;
			s->dir = 0;
		}
	}

	if (s->wrap) {
		last = - s->dir;
		s->in = (
			0 == last
			? 0
			: 0 < last
			? DBL_MIN
			: DBL_MAX 
		);
		for (n = s->last, c = m->input_count; c; --c, ++n)
			*n = last;
	}
}

static void module_trigger_processor(module m) {
	input in = m->named_inputs[0];
	output out = m->named_outputs[0];
	trigger_storage s = m->storage;
	int c;
	int where;

	input * i;
	output * o;
	int * n;

	/*
	 * initialize all outputs to 0
	 */

	for (c = m->input_count, o = m->outputs; c; --c, ++o) {
		**o = 0;
	}
	*out = 0;

	/*
	 * if we're looking for up crossings
	 * and wrapping is on
	 * and the input is lower than it was
	 * then for every point
	 * 	we were most recently below it
	 */
	if (
		s->wrap
		&& s->dir > 0
		&& **in < s->in
	)
		for (c = m->input_count, n = s->last; c; --c, ++n)
			*n = -1;
	/*
	 * if we're looking for down crossings
	 * and wrapping is on
	 * and the input is higher than it was
	 * then for every point 
	 * 	we were most recently above it
	 */
	if (
		s->wrap
		&& s->dir < 0
		&& **in > s->in
	)
		for (c = m->input_count, n = s->last; c; --c, ++n)
			*n = 1;
	/*
	 * for each trigger point
	 */
	for (
		c = m->input_count, 
		n = s->last,
		i = m->inputs,
		o = m->outputs;

		c;

		--c, ++n, ++i, ++o
	) {
		where = (
			**in == ***i
			? 0
			: **in < ***i
			? -1
			: 1
		);
	/*
	 * 	if we were most recently above it 
	 * 	and now we're below it
	 * 	and we're looking for down crossings
	 * 	then set this point's output to 1
	 * 	and set the main output to one
	 * 	because this is a down crossing for that point
	 *
	 * 	if we were most recently below it 
	 * 	and now we're above it
	 * 	and we're looking for up crossings
	 * 	then set this point's output to 1
	 * 	and set the main output to one
	 * 	because this is an up crossing for that point
	 */
		if (
			/* 
			 * WARNING: TRICKY (i.e. bad) CODE HERE
			 *
			 * we're assigning 'where' because we want to
			 * eliminat the problem of what happens when
			 * the input is equal to the crossing point.
			 * in this case where would be 0.  to avoid
			 * having a trigger on two successive samples
			 * we set it to 1 or -1. we're doing it here
			 * because we want to avoid extra operations.
			 *
			 * i can't explain it very well, which means i
			 * should probably rewrite this to be more
			 * clear.
			 */
			s->dir <= 0 && *n > where && (where = -1)
			|| s->dir >= 0 && *n < where && (where = 1)
		) {
			*out = **o = 1;
		}
		*n = where;
	}
	s->in = **in;
}
